package com.autumn.web.handlers;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import com.autumn.common.api.web.vo.AbstractApiResponse;
import com.autumn.common.api.web.vo.ApiObjectResponse;
import com.autumn.common.api.web.vo.ErrorInfoResult;
import com.autumn.spring.boot.context.AutumnContextEnvironment;
import com.autumn.util.StringUtils;
import com.autumn.web.annotation.IgnoreApiResponseBody;

/**
 * Aip 响应体处理器
 * 
 * @author 老码农
 *
 *         2018-01-14 21:24:50
 */
@Controller
@ControllerAdvice
@ResponseBody
public class ApiResponseBodyHandler extends AbstractErrorController implements ResponseBodyAdvice<Object> {

	private final static String SWAGGER_BEGIN = "springfox.";

	/**
	 * 日志
	 */
	protected final Log logger = LogFactory.getLog(this.getClass());

	private final IErrorInfoResultHandler errorInfoResultHandler;

	@Autowired
	private WebApplicationContext applicationContext;

	/**
	 * 
	 */
	protected final Set<String> apiControllerPackageSet;
	protected final HttpStatus[] errorHttpStatusArray;

	// value == true 时，表示忽略
	private Map<String, Boolean> API_URL_MAP = new ConcurrentHashMap<>();
	// value == true 时，表示忽略
	private Map<Method, Boolean> API_METHOD_MAP = new ConcurrentHashMap<>();

	private Object API_MAP_LOCK = new Object();
	private boolean isLoad = false;

	/**
	 * 实例化
	 * 
	 * @param errorInfoResultHandler
	 * @param apiControllerPackages
	 * @param errorHttpStatusArray
	 */
	public ApiResponseBodyHandler(IErrorInfoResultHandler errorInfoResultHandler, String[] apiControllerPackages,
			HttpStatus[] errorHttpStatusArray) {
		super(new DefaultErrorAttributes());
		this.errorInfoResultHandler = errorInfoResultHandler;
		this.apiControllerPackageSet = new HashSet<>();
		if (apiControllerPackages != null && apiControllerPackages.length > 0) {
			for (String pck : apiControllerPackages) {
				if (!StringUtils.isNullOrBlank(pck)) {
					this.apiControllerPackageSet.add(pck);
				}
			}
		}
		this.errorHttpStatusArray = errorHttpStatusArray;
		this.isLoad = false;
	}

	/**
	 * 初始化
	 * 
	 *
	 */
	protected void initialize() {
		if (this.isLoad) {
			return;
		}
		synchronized (API_MAP_LOCK) {
			if (this.isLoad) {
				return;
			}
			this.loadMap();
			this.isLoad = true;
		}
	}

	/**
	 * 
	* 
	*
	 */
	private void loadMap() {
		Map<RequestMappingInfo, HandlerMethod> map = this.getHandlerMethodMap();
		for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
			boolean imgore = isIgnore(entry.getValue());
			API_METHOD_MAP.put(entry.getValue().getMethod(), imgore);
			Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
			Set<String> patterns = entry.getKey().getPatternsCondition().getPatterns();
			for (String url : patterns) {
				for (RequestMethod mehhod : methods) {
					API_URL_MAP.put(url.toLowerCase() + "/" + mehhod.toString().toLowerCase(), imgore);
				}
			}
		}
	}

	/**
	 * 获取Url函数
	 * 
	 * @return 若指定包，则匹配，否则按所有
	 *
	 */
	private Map<RequestMappingInfo, HandlerMethod> getHandlerMethodMap() {
		RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
		Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
		if (this.apiControllerPackageSet.size() == 0) {
			return map;
		}
		Map<RequestMappingInfo, HandlerMethod> result = new HashMap<>();
		for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
			if (isContainPackage(entry.getValue())) {
				result.put(entry.getKey(), entry.getValue());
			}
		}
		return result;
	}

	/**
	 * 是否包含特定包
	 * 
	 * @param handlerMethod
	 *            函数
	 * @return
	 *
	 */
	private boolean isContainPackage(HandlerMethod handlerMethod) {		
		String name = handlerMethod.getBeanType().getPackage().getName();
		if (this.apiControllerPackageSet.contains(name)) {
			return true;
		}
		for (String pckName : this.apiControllerPackageSet) {
			if (name.startsWith(pckName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 是否忽略
	 * 
	 * @param handlerMethod
	 *            函数处理器
	 * @return
	 *
	 */
	private boolean isIgnore(HandlerMethod handlerMethod) {
		IgnoreApiResponseBody ignoreBody = handlerMethod.getMethod().getAnnotation(IgnoreApiResponseBody.class);
		if (ignoreBody != null) {
			return true;
		}
		if (AutumnContextEnvironment.INSTANCE.isEnableSwagger()) {
			String packageName = handlerMethod.getMethod().getDeclaringClass().getPackage().getName();
			if (packageName.startsWith(SWAGGER_BEGIN)) {
				return true;
			}
		}
		Class<?> type = handlerMethod.getMethod().getDeclaringClass();
		do {
			ignoreBody = type.getAnnotation(IgnoreApiResponseBody.class);
			if (ignoreBody != null) {
				return true;
			}
			type = type.getSuperclass();
		} while (!type.equals(Object.class));
		return false;
	}

	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		this.initialize();
		Boolean value = API_METHOD_MAP.get(returnType.getMethod());
		if (value == null || value.booleanValue()) {
			return false;
		}
		return true;
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
			ServerHttpResponse response) {
		if (body instanceof AbstractApiResponse) {
			return body;
		}
		return ApiObjectResponse.success(body);
	}

	/**
	 * 是否是 Api 请求
	 * 
	 * @param request
	 *            请求
	 * @return
	 *
	 */
	private boolean isApiRequest(HttpServletRequest request) {
		String url = request.getRequestURI().toLowerCase();
		Boolean value = API_URL_MAP.get(url + "/" + request.getMethod().toLowerCase());
		if (value == null || value.booleanValue()) {
			return false;
		}
		return true;
	}

	@SuppressWarnings("unused")
	private boolean isJsonRequest(HttpServletRequest request) {
		//暂时不用，无法取得  Content-Type
		String value = request.getHeader("Content-Type");
		return value != null && value.toLowerCase().contains("application/json");
	}

	/**
	 * 
	 * @param request
	 * @param e
	 * @return
	 * @throws Exception
	 */
	@ExceptionHandler(value = Throwable.class)
	public Object exceptionResponse(HttpServletRequest request, HttpServletResponse response, Throwable e) {
		this.initialize();
		ErrorInfoResult result = this.errorInfoResultHandler.getErrorInfo(request, e);
		if (result.isWriteLog()) {
			logger.error(e.getMessage(), e);
		}
		if (!isApiRequest(request)) {
			HttpStatus status = getStatus(request);
			response.setStatus(status.value());
			Map<String, Object> body = this.getErrorAttributes(request, false);
			body.put("message", e.getMessage());
			return body;
			/*if (this.isJsonRequest(request)) {
				return body;
			}
			return new ModelAndView("error", body);*/
		}
		return ApiObjectResponse.error(result.getErrorInfo().getCode(), result.getErrorInfo().getMessage(),
				result.getErrorInfo().getDetails());
	}

	/**
	 * api 根 url
	 */
	public final static String ERROR_RUL = "/api/error";

	/**
	 * 获取错误地址
	 * 
	 * @param request
	 * @return 2017-12-07 11:18:13
	 */
	@GetMapping(ERROR_RUL)
	public Object error(HttpServletRequest request, HttpServletResponse response) {
		this.initialize();
		Map<String, Object> body = this.getErrorAttributes(request, false);
		HttpStatus status = getStatus(request);
		if (!isApiRequest(request)) {
			response.setStatus(status.value());
			return body;
			/*if (this.isJsonRequest(request)) {
				return body;
			}
			return new ModelAndView("error", body);*/
		}
		String message = "未知异常";
		String details = "";
		if (body != null) {
			Object value;
			value = body.get("message");
			if (value != null) {
				message = value.toString();
			}
			value = body.get("path");
			if (value != null) {
				details = value.toString();
			}
		}
		return ApiObjectResponse.error(status.value(), message, details);
	}

	@Override
	public String getErrorPath() {
		return ERROR_RUL;
	}

	/**
	 * 重定向
	 * 
	 * @return
	 */
	@Bean
	public EmbeddedServletContainerCustomizer autumnApiContainerCustomizer() {
		final HttpStatus[] httpStatus = this.errorHttpStatusArray;
		return (container -> {
			if (httpStatus != null) {
				for (HttpStatus status : httpStatus) {
					ErrorPage errorPage = new ErrorPage(status, ERROR_RUL);
					container.addErrorPages(errorPage);
				}
			}
		});
	}
}
